iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 26
0
Software Development

系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!系列 第 26

Day 26: 您不可不知的FT2232H (3/3) - MPSSE & JTAG Control

  • 分享至 

  • xImage
  •  

0. 前言

經過上篇苦戰之後,終於進到最後講解JTAG訊號控制的部分!
本篇可能會有點長和無聊!
筆者盡量以程式碼配合範例的方式說明!
  
  
  

1. MPSSE JTAG Control Function

在開始介紹JTAG Operation之前,我們先來複習一下「Day 24: 您不可不知的FT2232H (2/3) - MPSSE Command Processor」中,"2. JTAG Control"的其中幾個會用到的Command,主要是控制TDI、TDO和TMS,
並看看實際程式碼!!
  
  

1.1 Write TDI Opertaion

以目前實作來說,主要是使用以下兩種Command,用來做TDI傳輸的部分

  • 0x19, LengthL, LengthH, Byte1, ....... , Byte65535: 從Byte1的LSB開始依序在-VE CLK的時候,從TDI送出,總共送出(LengthH | LengthL)+1個Byte
  • 0x1B, Length, Byte: 從LSB開始,在-VE CLK的時候,從TDI送出,總共送出Length+1個Bits

前者主要在TDI資料長度大於1個Byte的時候,以Bytes的方式送出;
後者主要用在資料長度小於1個Byte的時候,以Bits的方式送出

來看看實際程式實作的部分,請參考(src/jtag/drivers/mpsse.c):

void mpsse_clock_data(struct mpsse_ctx *ctx, const uint8_t *out, unsigned out_offset, uint8_t *in,
    unsigned in_offset, unsigned length, uint8_t mode)
{
    /* TODO: Fix MSB first modes */
    DEBUG_IO("%s%s %d bits", in ? "in" : "", out ? "out" : "", length);

    if (ctx->retval != ERROR_OK) {
        DEBUG_IO("Ignoring command due to previous error");
        return;
    }

    /* TODO: On H chips, use command 0x8E/0x8F if in and out are both 0 */
    if (out || (!out && !in))
        mode |= 0x10;
    if (in)
        mode |= 0x20;

    while (length > 0) {
        /* Guarantee buffer space enough for a minimum size transfer */
        if (buffer_write_space(ctx) + (length < 8) < (out || (!out && !in) ? 4 : 3)
                || (in && buffer_read_space(ctx) < 1))
            ctx->retval = mpsse_flush(ctx);

        if (length < 8) {
            /* Transfer remaining bits in bit mode */
            buffer_write_byte(ctx, 0x02 | mode);
            buffer_write_byte(ctx, length - 1);
            if (out)
                out_offset += buffer_write(ctx, out, out_offset, length);
            if (in)
                in_offset += buffer_add_read(ctx, in, in_offset, length, 8 - length);
            if (!out && !in)
                buffer_write_byte(ctx, 0x00);
            length = 0;
        } else {
            /* Byte transfer */
            unsigned this_bytes = length / 8;
            /* MPSSE command limit */
            if (this_bytes > 65536)
                this_bytes = 65536;
            /* Buffer space limit. We already made sure there's space for the minimum
             * transfer. */
            if ((out || (!out && !in)) && this_bytes + 3 > buffer_write_space(ctx))
                this_bytes = buffer_write_space(ctx) - 3;
            if (in && this_bytes > buffer_read_space(ctx))
                this_bytes = buffer_read_space(ctx);

            if (this_bytes > 0) {
                buffer_write_byte(ctx, mode);
                buffer_write_byte(ctx, (this_bytes - 1) & 0xff);
                buffer_write_byte(ctx, (this_bytes - 1) >> 8);
                if (out)
                    out_offset += buffer_write(ctx,
                            out,
                            out_offset,
                            this_bytes * 8);
                if (in)
                    in_offset += buffer_add_read(ctx,
                            in,
                            in_offset,
                            this_bytes * 8,
                            0);
                if (!out && !in)
                    for (unsigned n = 0; n < this_bytes; n++)
                        buffer_write_byte(ctx, 0x00);
                length -= this_bytes * 8;
            }
        }
    }
}

好,結束XDD 太長了.....!

一樣,我們拆成N個部份來解釋!
這邊先提醒一下,參數中的mode如果沒有特別說明,一律用以下JTAG_MODE的設定!

// src/jtag/drivers/mpsse.h
#define POS_EDGE_OUT 0x00
#define POS_EDGE_IN 0x00
#define LSB_FIRST 0x08

// src/jtag/drivers/ftdi.c
#define JTAG_MODE (LSB_FIRST | POS_EDGE_IN | NEG_EDGE_OUT)

首先是判斷欲執行的動作是否包含寫入TDI,或是讀取TDO:

    if (out || (!out && !in))
        mode |= 0x10;
    if (in)
        mode |= 0x20;

並將對應的Bit 4/5拉起來,還記的以下編碼的規則嗎XD!?

  • Bit 0: 在CLK Falling edge(-VE CLK)時,送出TDI/TMS!? (1:Yes / 0: No)
  • Bit 1: 資料為bit/byte模式 (1: Bit mode / 0: Byte mode)
  • Bit 2: 在CLK Falling edge(-VE CLK)時,從TDO接收資料!? (1:接收 / 0:不接收)
  • Bit 3: LSB/MSB優先傳輸? (1: LSB / 0: MSB)
  • Bit 4: TDI傳輸? (1:傳輸 / 0:不傳輸)
  • Bit 5: 從TDO接收資料? (1:接收 / 0:不接收)
  • Bit 6: TMS傳輸? (1:傳輸 / 0:不傳輸)
  • Bit 7: 保持0

再來是迴圈處理的部分,簡單的來說就是把Data拆成Bytes和Bits兩部分來處理!
首先是Bits傳輸的部分:

            /* Transfer remaining bits in bit mode */
            buffer_write_byte(ctx, 0x02 | mode);
            buffer_write_byte(ctx, length - 1);
            if (out)
                out_offset += buffer_write(ctx, out, out_offset, length);
            if (in)
                in_offset += buffer_add_read(ctx, in, in_offset, length, 8 - length);
            if (!out && !in)
                buffer_write_byte(ctx, 0x00);
            length = 0;

這邊很簡單,基本上就是用

  • "0x1B, Length, Byte": 純寫TDI
  • "0x2A, Length": 純讀TDO
    的方式,把資料傳輸出去!

再來是Bytes傳輸的部分,首先是檢查一下資料的長度不能大於65536個Bytes(傳輸上限):

           /* Byte transfer */
            unsigned this_bytes = length / 8;
            /* MPSSE command limit */
            if (this_bytes > 65536)
                this_bytes = 65536;
            /* Buffer space limit. We already made sure there's space for the minimum
             * transfer. */
            if ((out || (!out && !in)) && this_bytes + 3 > buffer_write_space(ctx))
                this_bytes = buffer_write_space(ctx) - 3;
            if (in && this_bytes > buffer_read_space(ctx))
                this_bytes = buffer_read_space(ctx);

接著是傳輸Bytes:

            if (this_bytes > 0) {
                buffer_write_byte(ctx, mode);
                buffer_write_byte(ctx, (this_bytes - 1) & 0xff);
                buffer_write_byte(ctx, (this_bytes - 1) >> 8);
                if (out)
                    out_offset += buffer_write(ctx,
                            out,
                            out_offset,
                            this_bytes * 8);
                if (in)
                    in_offset += buffer_add_read(ctx,
                            in,
                            in_offset,
                            this_bytes * 8,
                            0);
                if (!out && !in)
                    for (unsigned n = 0; n < this_bytes; n++)
                        buffer_write_byte(ctx, 0x00);
                length -= this_bytes * 8;

依照不同狀況,分別使用對應的MPSSE Command:

  • "0x19, LengthL, LengthH, Byte1, ....... , Byte65535": 從Byte1的LSB開始依序在-VE CLK的時候,從TDI送出,總共送出(LengthH | LengthL)+1個Byte
  • "0x28, LengthL, LengthH": 在+VE CLK的時候,依序從LSB開始讀取,共(LengthH | LengthL)+1個Byte
      
      

1.2 Write TMS Opertaion

這邊的TMS Opertaion主要是用在JTAG TAP State Machine的控制上,讓JTAG進入到指定的State中!
一樣先上程式碼,請參考(src/jtag/drivers/mpsse.c):

void mpsse_clock_tms_cs(struct mpsse_ctx *ctx, const uint8_t *out, unsigned out_offset, uint8_t *in,
    unsigned in_offset, unsigned length, bool tdi, uint8_t mode)
{
    DEBUG_IO("%sout %d bits, tdi=%d", in ? "in" : "", length, tdi);
    assert(out);

    if (ctx->retval != ERROR_OK) {
        DEBUG_IO("Ignoring command due to previous error");
        return;
    }

    mode |= 0x42;
    if (in)
        mode |= 0x20;

    while (length > 0) {
        /* Guarantee buffer space enough for a minimum size transfer */
        if (buffer_write_space(ctx) < 3 || (in && buffer_read_space(ctx) < 1))
            ctx->retval = mpsse_flush(ctx);

        /* Byte transfer */
        unsigned this_bits = length;
        /* MPSSE command limit */
        /* NOTE: there's a report of an FT2232 bug in this area, where shifting
         * exactly 7 bits can make problems with TMS signaling for the last
         * clock cycle:
         *
         * http://developer.intra2net.com/mailarchive/html/libftdi/2009/msg00292.html
         */
        if (this_bits > 7)
            this_bits = 7;

        if (this_bits > 0) {
            buffer_write_byte(ctx, mode);
            buffer_write_byte(ctx, this_bits - 1);
            uint8_t data = 0;
            /* TODO: Fix MSB first, if allowed in MPSSE */
            bit_copy(&data, 0, out, out_offset, this_bits);
            out_offset += this_bits;
            buffer_write_byte(ctx, data | (tdi ? 0x80 : 0x00));
            if (in)
                in_offset += buffer_add_read(ctx,
                        in,
                        in_offset,
                        this_bits,
                        8 - this_bits);
            length -= this_bits;
        }
    }
}

首先將對應Write TMS的Bit拉起來,並判斷是否要同時讀取TDO

  • Bit 0: 在CLK Falling edge(-VE CLK)時,送出TDI/TMS!? (1:Yes / 0: No)
  • Bit 1: 資料為bit/byte模式 (1: Bit mode / 0: Byte mode)
  • Bit 2: 在CLK Falling edge(-VE CLK)時,從TDO接收資料!? (1:接收 / 0:不接收)
  • Bit 3: LSB/MSB優先傳輸? (1: LSB / 0: MSB)
  • Bit 4: TDI傳輸? (1:傳輸 / 0:不傳輸)
  • Bit 5: 從TDO接收資料? (1:接收 / 0:不接收)
  • Bit 6: TMS傳輸? (1:傳輸 / 0:不傳輸)
  • Bit 7: 保持0
    mode |= 0x42;
    if (in)
        mode |= 0x20;

接著以7個Bits為一單位,依序將TMS送出:

        if (this_bits > 7)
            this_bits = 7;

        if (this_bits > 0) {
            buffer_write_byte(ctx, mode);
            buffer_write_byte(ctx, this_bits - 1);
            uint8_t data = 0;
            /* TODO: Fix MSB first, if allowed in MPSSE */
            bit_copy(&data, 0, out, out_offset, this_bits);
            out_offset += this_bits;
            buffer_write_byte(ctx, data | (tdi ? 0x80 : 0x00));
            if (in)
                in_offset += buffer_add_read(ctx,
                        in,
                        in_offset,
                        this_bits,
                        8 - this_bits);
            length -= this_bits;
        }

送出的方法也很簡單啦,主要是用以下Command:

  • "0x4B, Length, Byte1": 從Byte1的LSB開始,依序將0~Length+1個Bit,在-VE CLK的時候,寫入TMS中!
  • "0x6B, Length, Byte1": 從Byte1的LSB開始,依序將0~Length+1個Bit,在+VE CLK的時候,寫入TMS中,並在-VE的時候從TDO中讀取資料

別忘記這邊有個小小"tricky"的地方!
就是Byte1的Bit 7在TMS開始傳輸前,「會被放到TDI上」

bool tdi就是用來判斷TDI是否需要特別寫出"1"!
  
  
  

2. JTAG Operation Overview (以讀取IDCODE為例子)

看完上面底層MPSSE的操作後,接下來我們來研究下JTAG Command是如何被執行的!

一般在上層的應用當中,會先將所需要讀/寫資料的動作轉成對應的JTAG Command後,
推入OpenOCD內部的JTAG Queue中先存放,比方說下面這個例子:

EX: 讀取IDCODE

static int scan_idcode(struct target *target)
{
        struct scan_field field;
        uint8_t in_value[4];

        jtag_add_ir_scan(target->tap, &select_idcode, TAP_IDLE);
        field.num_bits = 32;
        field.out_value = NULL;
        field.in_value = in_value;
        jtag_add_dr_scan(target->tap, 1, &field, TAP_IDLE);

        int retval = jtag_execute_queue();
        if (retval != ERROR_OK) {
                LOG_ERROR("failed jtag scan: %d", retval);
                return retval;
        }

        uint32_t in = buf_get_u32(field.in_value, 0, 32);
        LOG_DEBUG("IDCODE: 0x0 -> 0x%x", in);
        retrun ERROR_OK
}

從這個例子當中,可以看到讀取IDCODE這件事可以分成三個步驟:

  1. Add IR Scan 0x01(IDCODE): 將0x01推入IR中
  2. Add DR Scan: 從DR中讀取出剛剛執行IR的結果
  3. Execute JTAG Queue: 開始執行上面兩個動作

接下來重點來啦,讓我們先看看JTAG中TAP的State Machine(狀態機):

https://ithelp.ithome.com.tw/upload/images/20180113/20107327zcUstg8jJg.png
---引用自OpenOCD Developer's Guide - OpenOCD JTAG Primer
這邊先說一下,不曉得為啥外部連結圖片的功能突然顯示不出來,只好直接上傳到這邊

讓我們將上面三個步驟拆成N個詳細的JTAG操作(假設狀態機一開始就在Run-Test/Idle中,IR指令的長度為5-bits):

  1. Path move: 從 Run-Test/Idle 到 Shift-IR中
  2. Shift-IR 0x01: 從LSB開始在TDI上敲入0x01,總共5-bits
  3. Path move: 從 Shift-IR 到 Exit1-IR
  4. Path move: 從 Exit1-IR 到 Pause-IR
  5. Path move: 從 Pause-IR 回到 Run-Test/Idle -->到這邊,IR Scan已經完畢
  6. Path move: 從 Run-Test/Idle 到 Shift-DR中
  7. Shift-DR: 從LSB開始,在TDO上讀回IDCODE,總共32-bits
  8. Path move: 從 Shift-DR 到 Exit1-DR
  9. Path move: 從 Exit1-DR 到 Pause-DR
  10. Path move: 從 Pause-DR 回到 Run-Test/Idle -->到這邊,DR Scan已經完畢

不過這個步驟是有問題的!!!

請看Step 2地方! 這邊應該只需要敲入4-bits,剩下的一個bits,應該留到Step 3,
Path Move敲入TSM = 1的時候,同時把TDI = MSB的0x01 = 0x0敲入!

同理,Step 7的地方,這邊不需要讀32-bits,應該是讀31-bits才對!

整個詳細的步驟變成下圖:

https://ithelp.ithome.com.tw/upload/images/20180113/20107327QvYexeXibd.png
  
  
  

3. Execute JTAG Operation

以上概念講完了,回到程式碼中,讓我們看看jtag_execute_queue()中做了什麼事情,
請參考(src/jtag/core.c、src/jtag/drivers/driver.c)

int jtag_execute_queue(void)
{
    jtag_execute_queue_noclear();
    return jtag_error_clear();
}

void jtag_execute_queue_noclear(void)
{
    jtag_flush_queue_count++;
    jtag_set_error(interface_jtag_execute_queue());

....後面不重要
}


int interface_jtag_execute_queue(void)
{
    static int reentry;

    assert(reentry == 0);
    reentry++;

    int retval = default_interface_jtag_execute_queue();
    
    ....後面不重要
}

int default_interface_jtag_execute_queue(void)
{
    if (NULL == jtag) {
        LOG_ERROR("No JTAG interface configured yet.  "
            "Issue 'init' command in startup scripts "
            "before communicating with targets.");
        return ERROR_FAIL;
    }

    int result = jtag->execute_queue();

    ....後面不重要
}

走到這裡,終於看到jtag->execute_queue(),也就是呼叫ftdi_execute_queue()來處理這些JTAG Commands!
  
  

2.1 FTDI Execute Queue

基本上Execute Queue很簡單,就是把JTAG Command Queue中的Command一個個讀出來,
然後判斷要執行的動作,再呼叫對應處理的函式去處理!

ftdi_execute_queue()內容如下,請參考(src/jtag/drivers/ftdi.c):

static int ftdi_execute_queue(void)
{
    /* blink, if the current layout has that feature */
    struct signal *led = find_signal_by_name("LED");
    if (led)
        ftdi_set_signal(led, '1');  ///譯註: 閃爍LED,基本上就是把那根訊號的Value從0變1或是1變0


    for (struct jtag_command *cmd = jtag_command_queue; cmd; cmd = cmd->next) {
        /* fill the write buffer with the desired command */
        ftdi_execute_command(cmd);  ///譯註: 一個個執行JTAG Command
    }

    if (led)
        ftdi_set_signal(led, '0');

    int retval = mpsse_flush(mpsse_ctx);
    if (retval != ERROR_OK)
        LOG_ERROR("error while flushing MPSSE queue: %d", retval);

    return retval;
}

然後在ftdi_execute_command()中就會呼叫對應的處理函式,
請參考(src/jtag/drivers/ftdi.c):

static void ftdi_execute_command(struct jtag_command *cmd)
{
    switch (cmd->type) {
        case JTAG_RESET:
            ftdi_execute_reset(cmd);
            break;
        case JTAG_RUNTEST:
            ftdi_execute_runtest(cmd);
            break;
        case JTAG_TLR_RESET:
            ftdi_execute_statemove(cmd);
            break;
        case JTAG_PATHMOVE:
            ftdi_execute_pathmove(cmd);
            break;
        case JTAG_SCAN:
            ftdi_execute_scan(cmd);     ///譯註: IR/DR Scan處理
            break;
        case JTAG_SLEEP:
            ftdi_execute_sleep(cmd);
            break;
        case JTAG_STABLECLOCKS:
            ftdi_execute_stableclocks(cmd);
            break;
        case JTAG_TMS:
            ftdi_execute_tms(cmd);
            break;
        default:
            LOG_ERROR("BUG: unknown JTAG command type encountered: %d", cmd->type);
            break;
    }
}

基本上接下來的章節只會提到ftdi_execute_scan(),其餘有興趣的讀者,
可以自行研究看看!
其實是筆者沒時間去研究其他的操作 囧rz
  
  

2.2 FTDI Execute Scan - ftdi_execute_scan()

先來個程式碼看看,請參考(src/jtag/drivers/ftdi.c):

static void ftdi_execute_scan(struct jtag_command *cmd)
{
    DEBUG_JTAG_IO("%s type:%d", cmd->cmd.scan->ir_scan ? "IRSCAN" : "DRSCAN",
        jtag_scan_type(cmd->cmd.scan));

    /* Make sure there are no trailing fields with num_bits == 0, or the logic below will fail. */
    while (cmd->cmd.scan->num_fields > 0
            && cmd->cmd.scan->fields[cmd->cmd.scan->num_fields - 1].num_bits == 0) {
        cmd->cmd.scan->num_fields--;
        LOG_DEBUG("discarding trailing empty field");
    }

    if (cmd->cmd.scan->num_fields == 0) {
        LOG_DEBUG("empty scan, doing nothing");
        return;
    }

    if (cmd->cmd.scan->ir_scan) {
        if (tap_get_state() != TAP_IRSHIFT)
            move_to_state(TAP_IRSHIFT);
    } else {
        if (tap_get_state() != TAP_DRSHIFT)
            move_to_state(TAP_DRSHIFT);
    }

    ftdi_end_state(cmd->cmd.scan->end_state);

    struct scan_field *field = cmd->cmd.scan->fields;
    unsigned scan_size = 0;

    for (int i = 0; i < cmd->cmd.scan->num_fields; i++, field++) {
        scan_size += field->num_bits;
        DEBUG_JTAG_IO("%s%s field %d/%d %d bits",
            field->in_value ? "in" : "",
            field->out_value ? "out" : "",
            i,
            cmd->cmd.scan->num_fields,
            field->num_bits);

        if (i == cmd->cmd.scan->num_fields - 1 && tap_get_state() != tap_get_end_state()) {
            /* Last field, and we're leaving IRSHIFT/DRSHIFT. Clock last bit during tap
             * movement. This last field can't have length zero, it was checked above. */
            mpsse_clock_data(mpsse_ctx,
                field->out_value,
                0,
                field->in_value,
                0,
                field->num_bits - 1,
                ftdi_jtag_mode);
            uint8_t last_bit = 0;
            if (field->out_value)
                bit_copy(&last_bit, 0, field->out_value, field->num_bits - 1, 1);
            uint8_t tms_bits = 0x01;
            mpsse_clock_tms_cs(mpsse_ctx,
                    &tms_bits,
                    0,
                    field->in_value,
                    field->num_bits - 1,
                    1,
                    last_bit,
                    ftdi_jtag_mode);
            tap_set_state(tap_state_transition(tap_get_state(), 1));
            mpsse_clock_tms_cs_out(mpsse_ctx,
                    &tms_bits,
                    1,
                    1,
                    last_bit,
                    ftdi_jtag_mode);
            tap_set_state(tap_state_transition(tap_get_state(), 0));
        } else
            mpsse_clock_data(mpsse_ctx,
                field->out_value,
                0,
                field->in_value,
                0,
                field->num_bits,
                ftdi_jtag_mode);
    }

    if (tap_get_state() != tap_get_end_state())
        move_to_state(tap_get_end_state());

    DEBUG_JTAG_IO("%s scan, %i bits, end in %s",
        (cmd->cmd.scan->ir_scan) ? "IR" : "DR", scan_size,
        tap_state_name(tap_get_end_state()));
}

落落長! 沒關係!
我們就用上面章節介紹到讀取IDCODE為例子,來分析一下程式碼的部分,
並對照Log,看看實際執行的結果!
讓我們開始吧!!

首先一開始會判斷目前的Scan種類,區分成IR-Scan和DR-Scan:

    if (cmd->cmd.scan->ir_scan) {
        if (tap_get_state() != TAP_IRSHIFT)
            move_to_state(TAP_IRSHIFT);
    } else {
        if (tap_get_state() != TAP_DRSHIFT)
            move_to_state(TAP_DRSHIFT);
    }

以IDCODE的例子來說,第一個Scan是IR-Scan,也就是執行move_to_state(TAP_IRSHIFT);,將目前的狀態機從"RUN/IDLE"移動到"IRSHIFT",簡單的來說就是對TDI依序寫入1(LSB) -> 1 -> 0 -> 0(MSB)!
總共長度為4-bits、資料轉成Byte為0x3(0011)!

讓我們來瞧瞧實際執行的情況!

Debug: 40752 821 ftdi.c:265 move_to_state(): start=RUN/IDLE goal=IRSHIFT
Debug: 40753 821 ftdi.c:269 move_to_state(): tap_set_state(DRSELECT)
Debug: 40754 821 ftdi.c:269 move_to_state(): tap_set_state(IRSELECT)
Debug: 40755 821 ftdi.c:269 move_to_state(): tap_set_state(IRCAPTURE)
Debug: 40756 821 ftdi.c:269 move_to_state(): tap_set_state(IRSHIFT)
Debug: 40757 821 mpsse.c:573 mpsse_clock_tms_cs(): out 4 bits, tdi=0
Debug: 40758 821 mpsse.c:455 buffer_write_byte(): 4b
Debug: 40759 821 mpsse.c:455 buffer_write_byte(): 03
Debug: 40760 821 mpsse.c:455 buffer_write_byte(): 03

你看看! 你看看! 就是這麼簡單!!!!

既然到IRSHIFT,再來就是把IDCODE(0x1)給輸入TDI中:

            mpsse_clock_data(mpsse_ctx,
                field->out_value,
                0,
                field->in_value,
                0,
                field->num_bits - 1,
                ftdi_jtag_mode);

注意IR的長度雖然為5-bits,不過這邊只需要將前4-bit寫入就行了!
以下是實際執行的情況:

Debug: 40762 821 mpsse.c:497 mpsse_clock_data(): out 4 bits
Debug: 40763 821 mpsse.c:455 buffer_write_byte(): 1b
Debug: 40764 821 mpsse.c:455 buffer_write_byte(): 03
Debug: 40765 821 mpsse.c:463 buffer_write(): 4 bits

再來就是將IR最後的MSB敲出,並在TMS上輸入1,將狀態機從IRSHITF離開,進入到IREXIT1中:

            uint8_t last_bit = 0;
            if (field->out_value)
                bit_copy(&last_bit, 0, field->out_value, field->num_bits - 1, 1);
            uint8_t tms_bits = 0x01;
            mpsse_clock_tms_cs(mpsse_ctx,
                    &tms_bits,
                    0,
                    field->in_value,
                    field->num_bits - 1,
                    1,
                    last_bit,
                    ftdi_jtag_mode);
            tap_set_state(tap_state_transition(tap_get_state(), 1));

來看實際成果:

Debug: 40766 821 mpsse.c:573 mpsse_clock_tms_cs(): out 1 bits, tdi=0
Debug: 40767 821 mpsse.c:455 buffer_write_byte(): 4b
Debug: 40768 821 mpsse.c:455 buffer_write_byte(): 00
Debug: 40769 821 mpsse.c:455 buffer_write_byte(): 01
Debug: 40770 821 ftdi.c:494 ftdi_execute_scan(): tap_set_state(IREXIT1)

完美!
接下來就是從IREXIT1,移動到IRPAUSE:

            mpsse_clock_tms_cs_out(mpsse_ctx,
                    &tms_bits,
                    1,
                    1,
                    last_bit,
                    ftdi_jtag_mode);
            tap_set_state(tap_state_transition(tap_get_state(), 0));

Log:

Debug: 40771 821 mpsse.c:573 mpsse_clock_tms_cs(): out 1 bits, tdi=0
Debug: 40772 821 mpsse.c:455 buffer_write_byte(): 4b
Debug: 40773 821 mpsse.c:455 buffer_write_byte(): 00
Debug: 40774 821 mpsse.c:455 buffer_write_byte(): 00
Debug: 40775 822 ftdi.c:501 ftdi_execute_scan(): tap_set_state(IRPAUSE)

最後,當然要從目前的"IRPAUSE"回到"RUN/IDLE"的狀態啦!

    if (tap_get_state() != tap_get_end_state())
        move_to_state(tap_get_end_state());

Log:

Debug: 40776 822 ftdi.c:265 move_to_state(): start=IRPAUSE goal=RUN/IDLE
Debug: 40777 822 ftdi.c:269 move_to_state(): tap_set_state(IREXIT2)
Debug: 40778 822 ftdi.c:269 move_to_state(): tap_set_state(IRUPDATE)
Debug: 40779 822 ftdi.c:269 move_to_state(): tap_set_state(RUN/IDLE)
Debug: 40780 822 mpsse.c:573 mpsse_clock_tms_cs(): out 3 bits, tdi=0
Debug: 40781 822 mpsse.c:455 buffer_write_byte(): 4b
Debug: 40782 822 mpsse.c:455 buffer_write_byte(): 02
Debug: 40783 822 mpsse.c:455 buffer_write_byte(): 03

以上就是簡單的IR Scan分析!
DR Scan也是類似的方式,就不多做說明! 篇幅太長,打字很累!
  
  
  

99. 結語

痾! 花了這麼長的篇幅,終於將JTAG Command轉MPSSE Command的部分剖析完畢!
完整的 剖析了整個FT2232H底層的運作和實際OpenOCD5中MPSSE程式碼的部分!

如果對上述內容還是有不清楚的地方,建議把JTAG TAP State Machine印出來,
然後用紙筆來模擬訊號的進出,相信對整個流程會有更深入的了解!!!

真是一場鏖戰,打完這篇大概花了我半條命......!
  
  
  

參考資料

  1. FT2232H Dual High Speed USB to Multipurpose UART/FIFO IC Datasheet Version 2.5
  2. AN_108 Command Processor for MPSSE and MCU Host Bus Emulation Modes
  3. Day 24: 您不可不知的FT2232H (2/3) - MPSSE Command Processor
  4. fpga4fun.com How JTAG works

上一篇
Day 25: 您不可不知的FT2232H (2.5/3) - MPSSE Initial
下一篇
Day 27: 高手不輕易透露的技巧(1/2) - Flash Programming
系列文
系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言